/**  Chatroom Client class:
 * 	It is GUI interface representing a visitor connected to the chatroom,
 * 	and the interface allows a visitor to:
 * 	  1. connects to chatroom server after visitor types his/her name.
 * 	  2. sends a message and sends the message out to
 * 	     other visitors in the chatroom.
 * 	  3. receives messages from visitors.
 * 	  4. maintains a list of visitors so that private messages can be
 * 	     sent to selected visitor(s).
*/
// package chatroom;

import java.awt.* ;
import java.awt.event.* ;
import javax.swing.* ;
import javax.swing.border.* ;
import java.io.* ;
import java.net.* ;
import java.util.* ;
	
public class Client extends JApplet implements ActionListener, MouseListener  {

   String	hostAddress = "sleipnir.cs.csubak.edu";
   int		port = 8989;
   JLabel	lbArray[] = { new JLabel("Name"), new JLabel("Public Message") };
   public JTextField	txtArray[] = { new JTextField(), new JTextField() };
   JTextPane	ta = new JTextPane() ;
   JPanel	plArray[] = { new JPanel(), new JPanel() };
   JPanel	plSouth = new JPanel();
   JPanel	plEast = new JPanel();
   Socket	chatSocket = null;
   ObjectOutputStream	out = null;  
   ObjectInputStream	in  = null; 

   boolean	connected = false;
   boolean	privateMessage = false;
   boolean      justChecked    = false;
   Message 	message = new Message();
   String  	visitorName = "";
   Vector<Visitor>	visitors = new Vector<Visitor>(10);
   String	privateList = "";

   Container		c =  null;
   static JApplet	applet;

   public Client() { }
   
   public void init() {
	c = getContentPane();
	plEast.setBorder( new TitledBorder("User List"));
	plEast.setPreferredSize(new Dimension(100, 400));
        plEast.setFont(new Font("verdana", Font.BOLD, 10));	
	plEast.addMouseListener( this );

	c.add(plEast, BorderLayout.EAST);

	plSouth.setLayout(new BorderLayout());
	c.setBackground(Color.black);
	ta.setBackground(Color.black);
	ta.setForeground(Color.white); 
	ta.setForeground(Color.white);
	for ( int i = 0; i < plArray.length; i ++ ) {
			plArray[i].setLayout(new FlowLayout());
			plArray[i].add(lbArray[i]);
			plArray[i].add(txtArray[i]);
			txtArray[i].addActionListener(this);
			if ( i == 1 ) lbArray[i].addMouseListener(this);
			txtArray[i].setEnabled( i == 0 );
	}
	txtArray[0].setColumns(8); txtArray[0].setForeground(Color.blue);
	txtArray[1].setColumns(30);
	txtArray[1].setToolTipText("Enter message to send and press ENTER");
	lbArray[1].setToolTipText("Click to toggle between public & private message");
	plSouth.add(plArray[0], BorderLayout.WEST) ;
	plSouth.add(plArray[1], BorderLayout.CENTER);

	ta.setFont(new Font("verdana", Font.BOLD, 12) );
	c.add(new JScrollPane(ta), BorderLayout.CENTER);
	c.add(plSouth, BorderLayout.SOUTH);

   }

   public void stop() {
     try {
	// Make a LOGOUT message, and send out.
	message.set(Message.LOGOUT, visitorName, "");
	Thread thr = new ClientSender( out, new Message(message));
	thr.start() ;
	System.out.println("stop called, and LOGOUT is sent out.");
	try { thr.join(); } catch ( InterruptedException e ) { e.printStackTrace(); }
	if ( in != null ) in.close(); 
	if ( out != null ) out.close();
	if ( chatSocket != null ) chatSocket.close();
	out = null; in = null; message = null;
     } catch (IOException e ) {e.printStackTrace();}
   }

   public void paint( Graphics g) {
       super.paint(g);
       // paintVisitors();
   }

   final int xStart = 8, yStart = 30, yGap = 12;

   public void checkVisitor( int y ) {
       if ( visitors.size() < 1 ) return;
       int yMin = yStart - yGap, yMax = yMin + yGap * visitors.size();
       if ( y <= yMin || y >= yMax ) return;
       k = ( y - yMin ) / yGap;
       Visitor v = visitors.get(k);
       v.checked = ! v.checked;
       if ( ! v.name.equals( visitorName ) ) justChecked = v.checked;
       paintVisitors();
   }

   // Check whether visitor is checked for private message:
   //     -1 : no one is checked.
   //     0  : visitor himself/herself is checked.
   //     1  : at least one other visitor is checked.
   public int checkChecks ( ) {
       int checkNo = -1;
       Visitor     v = null;
       if ( visitors.size() < 1 ) return -1;
       for ( int i = 0; i < visitors.size(); i ++ ) {
	      v = visitors.get(i);
	      if (v.checked ) 
	     	  if ( v.name.equals( visitorName ))  checkNo = 0 ;
		  else return 1;
       }
       return checkNo;
   }  


   public String getPrivateList( ) {
       if ( visitors.size() < 1 ) return "";
       StringBuffer pList = new StringBuffer(100); 
       Iterator<Visitor> itr = visitors.iterator();
       Visitor  v = null;
       while ( itr.hasNext() ) {
	  v = itr.next();
	  if ( v.checked ) pList.append (v.name + Message.NAME_SEPARATOR);
       }
       String s = pList.toString();
       if ( s.length() >1 ) return s.substring(0, s.length() - 1);
       return "";  
   }
   

   public void paintVisitors() {
       	     int  k = 0;
       Graphics g = plEast.getGraphics();

       g.setColor( plEast.getBackground() );
       g.fillRect(8, 20, plEast.getWidth() - 12, plEast.getHeight() - 25 );
       g.setColor( Color.blue);
       Iterator<Visitor> itr = visitors.iterator();
       while ( itr.hasNext() ) {
	   Visitor v = itr.next();
	   g.setColor( v.checked ? Color.RED : Color.BLACK);
	   g.drawString(v.name, xStart, yStart + k * yGap );
	   k++;
       }
   }
   
   void setPrivateMessage ( boolean priv ) {
	privateMessage = priv ;
	lbArray[1].setForeground(privateMessage? Color.red : Color.black);
	lbArray[1].setText( privateMessage? "Private Message": "Public Message");
	if ( ! privateMessage ) justChecked = false; 
   }

   void processLoginEvent () {

       if ( connected ) return;

	try {
	    	// Connect to server and get input and output streams through
		// socket.
		chatSocket = new Socket(hostAddress, port); 
		Thread.sleep(200);
		out = new ObjectOutputStream ( chatSocket.getOutputStream());
		in  = new ObjectInputStream( chatSocket.getInputStream());
	        // start a client reader for for this visitor
		new ClientReceiver(in, ta, visitors, this).start();
		
	        message.set( Message.LOGIN, visitorName, ""); 
		Thread thr = new ClientSender( out, message);
		thr.start() ;
        	try { thr.join(); } catch (InterruptedException f )  { }
		connected = true;
	}
	catch(IOException er) { ta.append(er.toString()); }
	catch(InterruptedException er2) { ta.append(er2.toString()); }
	
   }


   // Implementation of MouserListener
   
   public void mouseEntered( MouseEvent e ) { }
   public void mouseExited( MouseEvent e ) { }
   public void mousePressed( MouseEvent e ) { }
   public void mouseReleased( MouseEvent e ) { }
   public void mouseClicked( MouseEvent e ) {
       Object obj = e.getSource();
       if ( obj == lbArray[1] ) { // Private or public message label is clicked

	   setPrivateMessage( ! privateMessage );
	   return ;
       }
       if ( obj == plEast ) {
	   checkVisitor( e.getY() );
       }
   }
 
   // Implementation of Action Listener.
   static int k = 1;
   static String str = null;
   public void actionPerformed(ActionEvent e ) {
	Object obj = e.getSource();

	// Event Source: Visitor Name text field
	// Actions     : Make socket connection, start receiver and send
	// 	 a login message.
	if ( obj == txtArray[0] ) {
		visitorName = new String(txtArray[0].getText()).trim() ;
		if ( visitorName.equals("") ) {
		    JOptionPane.showMessageDialog( null, "Enter yur name and press ENTER!");
		    return;
		}
		processLoginEvent();
		txtArray[0].setEnabled(false);
		txtArray[1].setEnabled(true);
		return;
	}

	// Event source: message text field.
	// Action      : send a message.
	if ( obj == txtArray[1] ) {
		// get a string from text field.
	        str = new String(txtArray[1].getText()).trim();
		if ( str.equals("") ) return;
		if ( privateMessage && checkChecks() < 1 ) {
		    JOptionPane.showMessageDialog( null, "Try to send a private message\n!"
				    + "without selectedreceiver(s)!");
		    return;
		}
	 
		if ( justChecked && !privateMessage ) {
		    String msg = "Try to send a public message with some visitor(s)" +
				"\nselected selected for private message.\n" +
				"Do you want to comtinue?" ;
		    int    ans = JOptionPane.showConfirmDialog(null, msg);
		    switch( ans ) {
			case JOptionPane.YES_OPTION    : justChecked = false; break;
			case JOptionPane.NO_OPTION     : justChecked = false; return;
			case JOptionPane.CANCEL_OPTION : return;
		    }

		}

		if ( privateMessage )  {
		    privateList = getPrivateList();
		    message.set (Message.PRIVATE,  visitorName + ": " + str, privateList);
		} else
		    message.set(Message.PUBLIC, visitorName + ": " + str, ""); 

		Thread thr = new ClientSender( out, new Message(message) );
		thr.start() ;
		try { thr.join(); } catch (InterruptedException f )  { }

		txtArray[1].setText("");
	}
		
   }

   public static void main( String arg[] ) {
	JFrame fm =  new JFrame () ;
	fm.setBackground(Color.black);
	applet = new Client();
	applet.init();
	fm.setTitle("Chatroom Client: Running as appliction.") ;
	fm.addWindowListener( new WindowAdapter () {
		public void windowClosing(WindowEvent e) {
			System.out.println("Before stop called");
			applet.stop();
			System.exit(0); } } );
	fm.getContentPane().add( applet );
	fm.setSize( 600, 450);
	fm.setVisible(true);
  }


class ClientSender extends Thread {

   ObjectOutputStream	out;  // OutputStreamWrite derived from Socket.
   Message	message;  // Message from a chat room client to other clients
   
   public ClientSender ( ObjectOutputStream w, Message s)
   { 
       out = w; message = s ; 
   } 

   public void run() {
       if ( out == null || message == null || message.message == null || message.message.length() < 1 ) return;	
       try {
		out.writeObject( message ) ;
		if ( message.type == Message.PRIVATE ) 
			System.out.printf("Private Message : %s\n", message);
       } catch( IOException e )
       {  e.printStackTrace() ; }
   }
}


class ClientReceiver extends Thread {

   ObjectInputStream		in 	= null;  // read message from server.
   JTextPane			ta 	= null;  // Draw messages.
   Client			client  = null;  // Panel to draw visitors 
   Vector<Visitor>		visitors = null;
   Message 			message = null;

   public ClientReceiver ( ObjectInputStream i, JTextPane a, Vector<Visitor> vtr, Client c )
   { in = i;  ta = a ; client = c; visitors = vtr; } 


   void remove( String name ) {
       Visitor v = null;
       for (int i = 0; i < visitors.size(); i++ ) {
	   v = visitors.get(i);
           if ( v.name.equals( name ) ) {
	       visitors.remove( i );
	       return;
	   }
	   if ( v.name.compareTo(name ) > 0 ) return;
       }
    }
       
   boolean isIn( String name ) {
       if ( visitors.size() < 1 ) return false; 
       Iterator<Visitor>  itr = visitors.iterator();
       Visitor v = null;
       while ( itr.hasNext() ) {
	   v =  itr.next();
	   if ( v.name.equals ( name )  ) return true;
       }
       return false;
   }
       
   void addVisitors ( Vector<String> names ) {
      
   System.out.printf("visitors before add %d\n", visitors.size() ); 
       Iterator<String>   itr = names.iterator();
       String s = null;

       while ( itr.hasNext() ) {
	  s = itr.next();
	  if ( ! isIn( s ) ) visitors.add( s.equals(client.txtArray[0].getText()) ?
		  new Visitor(s, true): new Visitor( s )) ;
       }
       Collections.sort( visitors );
      System.out.printf("visitors after add %d\n", visitors.size() ); 
   }

   public void run() {
        if ( in == null || ta == null || client == null ) return;
	try {
	    while ( true ) {

		// Read message
		try {
		    message = (Message) in.readObject() ;
		} catch (ClassNotFoundException nfdEx) { ta.append("ClientReceiver error: " + nfdEx); }

		switch( message.type ) {
		    case Message.PUBLIC:
			ta.append(message.message + "\n" );
			break;
		    case Message.PRIVATE:
			ta.append(message.message +
				" - pri. to " + message.receivers + "\n" );
			break;
		    case Message.LOGIN:
			// Add a new visitor to the list of visitor displayed on visitor list.
			Vector<String> newReceivers = message.getReceiverNames();
			addVisitors( newReceivers );
			ta.append(message.message + ": logged in.\n" );
			client.paintVisitors();
			break;
		    case Message.LOGOUT:
			remove( message.message );
			ta.append(message.message + ": logged out.\n");
			client.paintVisitors( );
			break;
		}
		if ( ta.getText().length() > 0 )
		    ta.setCaretPosition(ta.getText().length()  - 1) ;
	   }
 	} catch( IOException e ) { ta.append("Chatroom server is down.\n");  }
   }
}
}
